package middleware

import (
	"errors"
	"net/http"
	"strings"
	"time"

	"soma-server/common"
	"soma-server/config"
	"soma-server/dto"
	modelSystem "soma-server/model/system"
	"soma-server/pkg/e"
	"soma-server/pkg/util"
	r "soma-server/response"
	serviceSystem "soma-server/service/system"
	"soma-server/vo"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

type JWTMiddleware struct {
	AccessSecret  []byte
	RefreshSecret []byte
	Timeout       int
	MaxRefresh    int
}

func InitAuth() (*JWTMiddleware, error) {
	authMiddleware := &JWTMiddleware{
		AccessSecret:  []byte(config.Conf.Jwt.AKey),
		RefreshSecret: []byte(config.Conf.Jwt.RKey),
		Timeout:       config.Conf.Jwt.Timeout,
		MaxRefresh:    config.Conf.Jwt.MaxRefresh,
	}
	return authMiddleware, nil
}

type Claims struct {
	User dto.UserInfoDto
	jwt.StandardClaims
}

// GetToken 获取accessToken和refreshToken
func (jm *JWTMiddleware) GetToken(user dto.UserInfoDto) (string, string, int64) {
	// accessToken 的数据
	aT := Claims{
		user,
		jwt.StandardClaims{
			Issuer:    "sekiro",
			IssuedAt:  time.Now().Unix(),
			ExpiresAt: time.Now().Add(time.Duration(jm.Timeout) * time.Hour).Unix(),
		},
	}
	// refreshToken 的数据
	rT := Claims{
		user,
		jwt.StandardClaims{
			Issuer:    "sekiro",
			IssuedAt:  time.Now().Unix(),
			ExpiresAt: time.Now().Add(time.Duration(jm.MaxRefresh) * time.Hour).Unix(),
		},
	}
	accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, aT)
	refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, rT)
	accessTokenSigned, err := accessToken.SignedString(jm.AccessSecret)
	if err != nil {
		common.Log.Errorln("获取Token失败，Secret错误")
		return "", "", 0
	}
	refreshTokenSigned, err := refreshToken.SignedString(jm.RefreshSecret)
	if err != nil {
		common.Log.Errorln("获取Token失败，Secret错误")
		return "", "", 0
	}
	return accessTokenSigned, refreshTokenSigned, aT.ExpiresAt
}

func (jm *JWTMiddleware) ParseRefreshToken(refreshTokenString string) (*Claims, bool, error) {
	refreshToken, err := jwt.ParseWithClaims(refreshTokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jm.RefreshSecret, nil
	})
	if err != nil {
		common.Log.Errorln(err)
		return nil, false, err
	}
	if claims, ok := refreshToken.Claims.(*Claims); ok && refreshToken.Valid {
		return claims, true, nil
	}

	return nil, false, errors.New("invalid token")
}

func (jm *JWTMiddleware) ParseAccessToken(accessTokenString string) (*Claims, bool, error) {
	accessToken, err := jwt.ParseWithClaims(accessTokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jm.AccessSecret, nil
	})
	if err != nil {
		v, _ := err.(*jwt.ValidationError)
		if v.Errors == jwt.ValidationErrorExpired {
			common.Log.Errorln("the token has expired")
		} else {
			return nil, false, errors.New("invalid token")
		}
	}
	if claims, ok := accessToken.Claims.(*Claims); ok && accessToken.Valid {
		return claims, false, nil
	}
	return nil, false, errors.New("invalid token")
}

func (jm *JWTMiddleware) LoginHandler(c *gin.Context) {
	var req vo.RegisterAndLoginRequest

	// 请求json绑定
	if err := c.ShouldBind(&req); err != nil {
		common.Log.Errorln(err)
		r.Response(c, http.StatusInternalServerError, e.INVALID_PARAMS, nil, false)
		return
	}

	// 密码通过RSA解密
	decodeData, err := util.RSADecrypt([]byte(req.Password), config.Conf.System.RSAPrivateBytes)
	if err != nil {
		common.Log.Errorln(err)
		r.Response(c, http.StatusInternalServerError, e.ERROR_AUTH_DECRYPT_PWD, nil, false)
		return
	}

	userService := serviceSystem.NewUserService()

	u := &modelSystem.User{
		Username: req.Username,
		Password: string(decodeData),
	}

	// 密码校验
	user, err := userService.Login(u)
	if err != nil {
		common.Log.Errorln(err)
		r.Response(c, http.StatusForbidden, e.ERROR_AUTH_CHECK_PWD, nil, false)
		return
	}

	activeRoles := []string{}
	for _, role := range user.Roles {
		activeRoles = append(activeRoles, role.Code)
	}

	// 返回双 token
	accessTokenString, refreshTokenString, expiresAt := jm.GetToken(*user)
	if accessTokenString == "" || refreshTokenString == "" {
		r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_GET_TOKEN, nil, false)
		return
	}
	r.Response(c, http.StatusOK, e.SUCCESS, gin.H{
		"accessToken":  accessTokenString,
		"refreshToken": refreshTokenString,
		"roles":        activeRoles,
		"expires":      expiresAt,
		"username":     user.Username,
		"userId":       user.ID,
	}, true)
}

// JWTAuthMiddleware 用鉴权到中间件
func (jm *JWTMiddleware) JWTAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 默认双Token放在请求头Authorization的Bearer中，并以空格隔开
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			c.Abort()
			r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_EMPTY_TOKEN, nil, false)
			return
		}
		parts := strings.Split(authHeader, " ")
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.Abort()
			r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_TOKEN_FORMAT, nil, false)
			return
		}
		parseToken, isUpd, err := jm.ParseAccessToken(parts[1])
		if err != nil {
			common.Log.Errorln(err)
			c.Abort()
			r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_NOT_VALID_TOKEN, nil, false)
			return
		}
		if isUpd {
			c.Abort()
			r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_EXPIRES_TOKEN, nil, false)
			return
		}
		c.Set("user", parseToken.User)
		c.Next()
	}
}

func (jm *JWTMiddleware) RefreshHandler(c *gin.Context) {
	var req vo.RefreshTokenRequest
	// 请求json绑定
	if err := c.ShouldBind(&req); err != nil {
		common.Log.Errorln(err)
		r.Response(c, http.StatusUnauthorized, e.INVALID_PARAMS, nil, false)
		return
	}
	// 提取refreshToken
	refreshToken := req.RefreshToken
	if refreshToken == "" {
		r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_EMPTY_TOKEN, nil, false)
		return
	}
	parseRefreshToken, isUpd, err := jm.ParseRefreshToken(refreshToken)
	if err != nil {
		common.Log.Errorln(err)
		r.Response(c, http.StatusUnauthorized, e.ERROR_AUTH_NOT_VALID_TOKEN, nil, false)
		return
	}
	// refreshToken 已经失效，需要刷新双Token
	if isUpd {
		accessTokenString, refreshTokenString, expiresAt := jm.GetToken(parseRefreshToken.User)
		r.Response(c, http.StatusOK, e.SUCCESS, gin.H{
			"accessToken":  accessTokenString,
			"refreshToken": refreshTokenString,
			"expires":      expiresAt,
		}, true)
		return
	}
}
